feat(qwen35): shard GDR state per TP rank + batched eager/graph decode under TP (+27B benchmark validation) - #946
Conversation
Phase 2b ported onto pegainfer-project#870: recurrent/conv state, GDR scratch, and the linear-attention weight surface are allocated and addressed per rank instead of replicated, which is what makes 27B TP2 fit on 2x48 GB cards. - weight_loader: additive stitch/shard loaders (2D row stitch for the fused qkv [q|k|v] segments, 1D element stitch for conv1d channels, bf16/f32 1D shards for dt_bias/A_log) - config: local_linear_* accessors mirroring the full-attn TP style; linear head divisibility fails closed in TensorParallelConfig - weights: per-rank stitched shards for in_proj_qkv/conv1d (head-local slices per segment), row shards for z/b/a, col shard for out_proj, dt_bias/A_log sliced, norm_weight kept replicated (head-shared); loader reserve uses TP-aware estimates - recurrent_state/decode_buffers/prefill_buffers: state and GDR scratch at local value-head/qkv sizes; capacity math derives from local allocation_bytes - batch_decode/prefill: local head counts into the GDR decode/conv/ Triton-AOT prefill chains, gated RMSNorm at local v heads, all-reduce after linear out_proj; batch_decode_full_attention_via_prefill is now TP-local so eager decode routes 27B TP2 group-6 full attention through prefill (was FlashInfer Unsupported group_size: 6) - tp_executor: worker capacity math and per-request state use the rank-local sizes; decode rows still run as a per-request bs=1 loop (batched in a follow-up) Recurrent/conv state is never all-reduced. Signed-off-by: Ziyang Zhang <hafuhafu@qq.com>
Port of the batched eager TP decode step onto pegainfer-project#870's worker structure: decode rows in one command now run as ONE batched forward per step on every rank plus one batched rank-0 sampling pass, instead of a per-request bs=1 loop. - run_decode_batch resolves every decode row's worker state in command order, builds a step-scoped LinearStatePointerTables over the whole batch (from_recurrent_refs(..., bs, ...)), runs one batch_decode_eager_logits forward, then rank 0 snapshots all requested logprob rows before one batched select_batch over per-row params - execute_decode_rows (used by both decode-only and unified steps) calls run_decode_batch once; per-row results fan out in command order - TpRequestState.linear_pointer_tables (capacity-1, decode-only) removed; ensure_prefill_state no longer builds it. The step-scoped table is rebuilt every step, so swap_remove retirement can't stale it Seeded rows keep per-row semantics: select_batch isolates each seeded row into its own single-row philox call keyed on (request seed, step 0), so seeded output stays independent of batch composition. Unseeded rows decorrelate via the per-step command seed, same as the single-GPU batched path. Reference (27B TP2, 2x RTX 4090, eager): 16 concurrent 256-token completions aggregate 24.9 -> 292.3 tok/s; single-request unchanged. Signed-off-by: Ziyang Zhang <hafuhafu@qq.com>
Signed-off-by: Ziyang Zhang <hafuhafu@qq.com>
…eval - tp-implementation.md: Phase 2a/2b + Step 3 batched-decode landing record - benchmarks/qwen35-27b-tp2-knowledge-eval.md: MMLU-Redux 94.09 vs 93.2, C-Eval 88.11 vs 90.5 (full runs, truncation rerun-merged); in band - scripts/eval_mc.py + eval_rerun_truncated.py: benchmark runner (OpenCompass/lm-eval recipe replicas over the chat completions API) - developer-onboarding: NCCL libnccl.so dlopen note Signed-off-by: Ziyang Zhang <hafuhafu@qq.com>
6b44a25 to
f56ce51
Compare
51c6a6a to
04438e3
Compare
P2c from docs/models/qwen35/tp-design.md. TP decode captures/replays per-bucket CUDA Graphs when --cuda-graph is set AND the TP-local decode GQA group has a compiled kernel (4B/9B TP2); 27B TP2 (group 6) keeps the batched eager path byte-for-byte under the gate. - Gate: drop the fail-closed TP+graph rejections in config.rs/lib.rs/ tp_executor.rs; log once when graph was requested but the group gate keeps decode eager. - State: scheduler owns dense decode slots (slot_idx on TP decode rows, slot_for_new_request at promote, compaction_after_retire on retire); workers hold a fixed-address BatchDecodeGraphState plus slot_map, D2D copy prefill state into the slot on the first decode row, and apply DropRequest compactions via move_slot_within with occupancy assertions (poison on mismatch). - Capture/replay: startup pre-capture sweep ported from qwen3 (Warmup via new warmup_tp_collective, Capture/Launch per bucket, Finalize) behind a 600 s abort watchdog; batch_decode_graph gains DecodeGraphUse (Serve/CaptureOnly/Replay); TP serving is replay-only; graphs drop before the NCCL comm. - Convenience executor API (model-local tests) keeps a slot tracker mirroring Qwen35Executor; scheduler flows pass explicit slots. Validation (2x RTX 4090, venv NCCL): lib 105/105; 9B TP2 HF gates eager + graph (sequential replay, bucket-straddling, post-compaction) pass within existing tolerances; 9B TP2 scheduler e2e eager+graph pass; serving_tp2 now launches with graph on; 27B TP2 HF+e2e pass unchanged (group-6 gate keeps eager, graph variant self-skips). Serving benchmark 9B TP2, 16 concurrent x 256 out: 767.15 tok/s graph vs 705.86 eager (+8.7% steady output, TPOT 20.04 vs 21.99 ms). Signed-off-by: Ziyang Zhang <hafuhafu@qq.com>
04438e3 to
cb4a7a8
Compare
|
@codex please review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb4a7a82eb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let graph_slot_reserve = if graph_enabled { | ||
| bucket_for(requested_max_batch) * recurrent_bytes | ||
| } else { |
There was a problem hiding this comment.
Size graph slot reserve after capacity downshift
When TP CUDA Graph is requested with a large --max-batch on a rank that has enough memory for a smaller effective batch, this preflight subtracts the graph-slot reserve for the requested bucket before calculating the downshifted capacity. For example, a launch at the default batch 64 can fail with effective capacity 0 even if the card could fit bucket 8 plus 8 transient prefill states, because the actual graph state allocated later is based on bucket_for(effective_max_batch), not bucket_for(requested_max_batch). Computing this reserve iteratively from the candidate effective batch would preserve the intended capacity reduction path.
Useful? React with 👍 / 👎.
|
|
||
|
|
||
| # ---------------------------------------------------------------- mmlu-redux | ||
| MMLUREDUX_RE = re.compile(r'([ABCD])') |
There was a problem hiding this comment.
Match standalone MMLU-Redux answer letters only
For MMLU-Redux, responses such as Answer: B or The answer is C match the A in Answer because the extractor later uses this regex as a first [ABCD] search. Chat models commonly include an answer prefix despite the prompt asking for a bare letter, so this silently scores many correct B/C/D outputs as A and corrupts the benchmark summary; the pattern should prefer answer markers or standalone option tokens.
Useful? React with 👍 / 👎.
Summary
Rebased onto current main (#870 landed its own Phase 1/2a). This PR carries the deltas #870 lacks, plus 27B benchmark validation:
2530dfc2): rank-local slices end to end (weight_loader stitch/shard loaders, per-rank stitched qkv/conv1d + row/col shards, state/buffers at local sizes, all-reduce after local linearout_proj, TP-local group-6 decode reroute). Acceptance: 27B TP2 now fits on 2×48 GB cards (Phase-1 replicated state OOMed).b3aa5e11): feat(qwen35): tp2 phase 2a #870'sexecute_decode_rowsran one bs=1 forward per request per tick; this addsrun_decode_batch— onebatch_decode_eager_logitsover all decode rows with a step-scoped pointer table, one batched rank-0select_batch, per-row fan-out. Measured: 16-concurrency aggregate 24.9 → 292.3 tok/s.0570313f): startup pre-capture sweep ported from qwen3 (warmup collectives per bucket message size, Capture/Launch per bucket, Finalize assert, 600 s watchdog), slot-indexed per-rank recurrent/conv state with scheduler-ownedslot_idx+ D2D compaction on retire, serve-time replay-only. Gate:enable_cuda_graph && local_decode_group_is_compiled(tp)— 4B/9B TP2 capture; 27B TP2 (group-6, TP-invariant) stays batched eager until group-6 batch-decode kernels exist. 9B TP2 serving bench: steady output 706 → 767 tok/s (+8.7%), TPOT −8.8%.b376772a,f56ce511): rebase + P2c landing records intp-implementation.md; 27B knowledge-benchmark parity snapshot indocs/benchmarks/qwen35-27b-tp2-knowledge-eval.md; runner scriptsscripts/eval_mc.py+scripts/eval_rerun_truncated.py.Validation (2× RTX 4090, --features qwen35, on this branch)
cargo check/fmtclean; lib suite 105/105Knowledge-benchmark parity (27B, chat completions, thinking on)
Scores were measured on the pre-rebase f4c6678 line; both lines pass identical logits gates, but rerun on this branch before citing parity externally (noted in the doc).
Follow-ups
Group-6 batch-decode kernels (unlocks 27B TP2 graphs), arbitrary TP device ordinals.